
Day 21 我們把整個機房六台核心主機的日誌收進同一個查詢介面,並建立了可比對的 WORM 封存流程 (只寫一次,讀取多次)。今天我們聚焦在網路安全中最常見、卻也最容易被誤解的一種日誌,也就是防火牆擋掉的連線紀錄。
被擋掉的封包情報成本極低,因為阻擋本來就是防火牆的基本職責,產生阻擋日誌只是順手記錄的副產品,但它價值極高,因為每一行拒絕紀錄都代表著「有人企圖連線到一個不該連的地方」。只要掌握來源 IP(Source)、**目的連接埠(Destination Port)與時間戳記(Timestamp)**三個欄位,就能回答大多數的安全疑問。
在最初的架構草稿中,我的預設前提是:
「機房內有四道防火牆,各自將阻擋紀錄送出,再用同一支程式去解析匯總。」
然而實際盤點現場後才發現殘酷的現實,四道牆裡有兩道根本沒開,第三道擋了但不記紀錄,邊界的核心 FortiGate 雖然一直在阻擋,卻預設一筆拒絕都沒記。
因此,本文將分為兩大部分:
本日程式碼已整併至 onprem-logs v0.2.0 的
firewall/目錄;告警規則則整併至 onprem-metrics v0.2.2。
經全面清查後,場域內涉及封包過濾的邊界與節點共有五處:
| 牆面位置 | 部署節點 | 現場盤點狀況 | 本日調整處置 | 日誌傳輸路徑 |
|---|---|---|---|---|
| FortiGate 60F | 網路邊界 | 持續阻擋中,但拒絕封包幾乎完全不記錄;記憶體 4.75 小時內的 1,099 筆全為 headscale 入站 policy 的 accept | 新增第二組 syslog 目標送往收集端,開啟阻擋攝影機外連政策的記錄功能 | syslogd2,TCP 514,RFC 5424 格式 |
| DOCKER-USER | 收集端主機 | Day 21 設定的白名單運作中,阻擋未授權連線但不留紀錄 | 在每條 DROP 前插入限速 LOG 規則,收集端自身同時上傳 journal | 收集端本機 journald,經 loopback 傳輸 |
| QuLog 連線紀錄 | 兩台 QNAP NAS | Day 21 已配置傳輸連線紀錄 | 校正解析欄位字串,清查 NAS 原生帳號封鎖門檻 | 沿用 Day 21 配置之 syslog 514 |
| ufw | 兩台 DGX Spark | 套件已安裝,但設定為 ENABLED=no |
評估後維持不啟用(詳見第二節) | 若啟用則為 Linux 核心訊息(_TRANSPORT=kernel) |
| pve-firewall | 兩台 PVE 虛擬化節點 | 狀態為 disabled/running |
評估後暫不開啟(詳見第二節),補齊啟用 SOP | 若啟用則透過 pvefw-journal.service 鏡射 |
雖然日誌來自五個不同位置,但本質上只有三種格式:
SRC= DST= PROTO= DPT=。在 LogsQL 中使用 extract " SRC=<src> " 抽詞。
注意:抽詞前後必須保留空格,因為網卡
MAC=欄位的值內部也包含等號,缺少空白邊界會導致解析錯位。
unpack_logfmt 函式。名稱: 值,」的正則模式抽取。報告分析腳本 fw-report.py 會將這五種來源統一抽象為四個通用欄位:src、dst、proto、dpt,讓後續的分析判讀,能完全解耦於底層來源。
在維運中,不變更往往比盲目開啟防護需要更謹慎的工程論證。
檢視兩台 Spark 的 /etc/ufw/ufw.conf,皆為 ENABLED=no。Day 11 的安全加固僅在容器內驗證,並未套用到實體機器。草稿中的腳本 spark-ufw-logging.sh 在遇到未啟用的 ufw 時會中斷執行,這是正確的行為:「開啟防火牆」是獨立的高風險變更,絕不能混雜在「開啟日誌」的操作中順便執行。
評估是否啟用主機 ufw 的權衡如下:
| 評估面向 | 啟用 ufw(預設拒絕入站) | 維持不啟用(現場環境現況) |
|---|---|---|
| 防護能力 | 測試時綁定在 0.0.0.0 的臨時服務(如 Jupyter、Ray Dashboard、Host 網路推論容器)不會對整段區網敞開 |
任何監聽中的 Port,整個內網網段皆可存取 |
| 日誌可見性 | 可擷取 [UFW BLOCK],掌握內網橫向探測行為 |
無主機端拒絕日誌 |
| 防護盲點 | Docker 對外映射的 Port(-p)在 PREROUTING 階段做 DNAT 後直接走 FORWARD 鏈,預設繞過 ufw INPUT 鏈 |
同左 |
| 維運成本 | 開發測試服務皆需 ufw allow;兩張 CX7 介面需整段放行,避免 NCCL 通訊中斷 |
無額外維運負擔 |
| 潛在風險 | 規則疏漏可能導致分散式訓練或叢集後端中斷 | 仰賴網路邊界設備防護與內網信任機制 |
決策結論:
這兩台機器專注於內網高效能運算與密集測試,且 Docker 服務預設即繞過 ufw,硬開 ufw 防不到容器,反而極易干擾 NCCL 跨機通訊。因此決定不啟用 ufw,防禦重心放在邊界。
作為補償措施,我們建立監聽連接埠基準盤點,spark02 對區網暴露的僅有 22(SSH)、9100(node_exporter)與 mDNS,其餘 15 個 NCCL 臨時 Port 皆嚴格綁定在 CX7 專屬網段(192.168.100.0/24)。這份盤點清單比一道疏於維護的 ufw 更能精準掌握主機暴露面。
原先想規劃「先在節點 A 啟用驗證,順利後再開節點 B」。但深入研讀 PVE 防火牆元件(pve-firewall 6.0.6)文件後,發現不能這樣子搞,原因如下:
cluster.fw 一旦寫入 enable: 1,所有節點將同步載入規則;個別節點的 host.fw 設定 enable: 0 僅會略過該節點的主機防護,無法做到乾淨的單節點隔離測試。net.bridge.bridge-nf-call-iptables 置為 1,導致所有 Linux Bridge(vmbr)轉發的 VM 流量全面經過 Host 的 iptables FORWARD 鏈。
pve1 上執行著 Docker,其預設的 FORWARD 鏈 policy 為 DROP。PVEFW-FORWARD 被附加在 FORWARD 的末端。management IPSet 會強制將整個本機所在網段併入。即便管理者在設定檔中限縮來源範圍,也無法將同網段的其他主機阻擋在 Web UI(8006)與 SSH 之外。所以不能貿然動 PVE 防火牆:若在
pve2貿然開啟防火牆,首當其衝斷線的將會是運行在pve1上的日誌收集伺服器。
決策結論:
PVE 防火牆暫不開啟,在 pve-firewall.md 中重新定義了正確的啟用程序:
DOCKER-USER 鏈放行 vmbr0 橋接流量,方可啟用叢集開關。net.bridge.bridge-nf-call-iptables 調回 0(系統停用時不會自動還原)。此外,鏡射日誌的 pvefw-journal.service 也修正了一個隱藏缺陷:原先設定了 LogLevelMax=notice,但這會連帶過濾掉 service 內透過 systemd-cat -p info 寫入的日誌。實測證明,若不拿掉此限制,日誌鏡射服務會無聲無息地丟棄所有阻擋紀錄。
透過 FortiOS REST API 備份並檢視既有政策:
policy 1(內網往外網全開):logtraffic 設為 utm,僅記錄觸發安全特徵的連線。policy 8(headscale 入站):設定為 all。policy 4(阻擋網路攝影機外連):設定為 disable(不記錄)。local-in-deny-unicast 預設關閉以保護記憶體環狀緩衝區。同時也找到一個之前沒想過的情況,也就是邊界防火牆每天默默擋下約 6.3 萬次攝影機對外連線,但在日誌系統中完全沒有留痕。 防火牆日誌最常見的失效模式不是解析錯誤,而是根本沒開記錄,而管理者往往誤以為邊界防護一定都有開。
FortiGate 支援四組獨立的 Syslog 目的地。為了不影響現有維運,我們啟用 syslogd2:
關鍵設定均由實測驗證:
rfc5424:default)格式缺乏 RFC syslog 標頭,VictoriaLogs 無法解析 hostname,且時間戳記會淪為收集端接收當下的時間;改採 rfc5424 後,hostname 精準帶出設備名稱,_time 保留事件發生當下含時區的時間戳。reliable):DOCKER-USER 必須預先將 FortiGate IP 加入白名單,否則首波日誌將被收集端拒於門外。forward-traffic 與 local-traffic。原先嘗試使用 category traffic 搭配 include 規則,但實測發現系統仍會發送 type="event"(如 DHCP、無線客戶端狀態);必須改用 category event 搭配 exclude 才能徹底攔截事件雜訊。最後,將關鍵的 policy 4 之 logtraffic 由 disable 切換為 all。
規則於 11:42 啟用記錄,隨後一小時(11:42~12:42)收集到的數據如下:
| 統計項目 | 觀測數據 | 數據意涵解析 |
|---|---|---|
| FortiGate 總接收筆數 | 2,920 筆 | 平均每分鐘約 48 筆 |
| Deny 阻擋筆數 | 2,631 筆 | 100% 來自 policy 4;每 10 分鐘 431~446 筆,流量非常平穩 |
| Deny 來源分佈 | 兩台攝影機 | .121 佔 1,915 筆(73%),.126 佔 716 筆(27%);第三台 .134 完全無紀錄 |
| Deny 目的 Port | NTP 123 與 HTTPS 443 | .121 主要對 9 組外部伺服器發送 NTP 請求;.126 主要連線 4 組外部 HTTPS 位址;兩者皆伴隨 DNS 查詢 |
| Policy 8 入站(WAN1) | 126 筆 | 來自 21 個外部 IP,其中單一 IP 佔 84 筆 |
| Policy 8 內部連內部 | 106 筆 | 內網主機經 WAN 存取 headscale 的 Hairpin NAT 流量 |
| 隱含拒絕(Policy 0) | 0 筆 | 無漏網之魚直接命中底層 Deny |
| Local Traffic | 57 筆 | 均為正常管理連線之 Accept 與 Close,無異常阻擋 |

這些紀錄證實了 policy 4 的有效性:攝影機持續不斷嘗試對外校時與回連雲端,在被擋下後未出現繞道行為。更重要的是,第三台攝影機完全沒出現,代表它可能已斷線或變更了 IP,這正是自動化報表應主動告警的異常狀況。
每小時約 2,920 筆,換算全天約 7 萬行日誌。相較 Day 21 全場域每日 8.3 萬行,日誌總量暴增了 84%。實際儲存空間換算如下:
| 儲存層級 | 每行大小 | 每日空間消耗 | 預期生命週期消耗 |
|---|---|---|---|
| 原始傳輸封包 | 約 674 Bytes | 約 46 MB | 串流傳輸,不落地 |
| VictoriaLogs 熱層 | 約 40 Bytes(剛寫入) | 1.5 ~ 2.8 MB | 90 天保存期約 130 ~ 250 MB |
| WORM 離線封存 | 壓縮後極小 | 約 2.2 MB | 180 天不可竄改封存約 400 MB |
| FortiGate 記憶體 Log | 設備上限約 20 MB | 每小時新增約 2,600 筆 | 約半天即覆蓋,GUI 僅能回溯最近半天 |
空間消耗在熱儲存層完全可控(90 天佔用不到 12 GiB 上限的 4%),但我們進行了兩項關鍵最佳化:
.121 攝影機每兩秒對 9 台時間伺服器發出 NTP 請求,佔了拒絕量的 65%。在 syslogd2 過濾器中加入 category traffic 的 (service NTP) exclude 規則後,10 分鐘日誌量即從 485 筆降至 186 筆(降幅達 62%)。剩餘的阻擋紀錄全為高價值的 HTTPS 與 DNS,每日日誌量大幅收斂至 2.7 萬行。archive-day.sh 中新增 ARCHIVE_SKIP 機制。MANIFEST.tsv 中會明確標註:
# skipped syslog-fgt-edge 4230 ARCHIVE_SKIP
確保合規稽核時能證明「此來源係依資安政策明確排除,而非日誌管線遺漏遺失」。
查詢阻擋連線的 LogsQL 語法範例如下:
# 查詢 FortiGate 24 小時內阻擋排行
_time:24h hostname:=fgt-edge "type=\"traffic\"" "action=\"deny\""
| unpack_logfmt from _msg fields (srcip, dstip, dstport, proto, policyid, action)
| filter action:=deny
| stats by (srcip, dstip, proto, dstport) count() as n
| sort by (n) desc
| limit 20
技巧:
action欄位是在unpack_logfmt解析後才產生,因此開頭必須先使用原始文字關鍵字"action=\"deny\""進行第一階段管線篩選,後續再透過filter action:=deny過濾。若順序顛倒,將查無資料。
收集端主機在 Day 21 透過腳本在 DOCKER-USER 鏈掛載了 ONPREM-LOGS 規則。若以手動方式執行 iptables -I 插入 LOG 規則,一旦 onprem-logs-allowlist.service 重啟,整條鏈會被清空重建,手動規則隨即失效。
因此,我們直接在 docker-user-allowlist.sh 內建 LOG_DROPS=1 機制:在每條 DROP 規則前插入條件相同、且具備 6 筆/分鐘限速 的 LOG 規則。
實測非授權來源探測:
pve1 企圖連線 514(pve1 僅在 9428 白名單內)。NAS 企圖連線 9428(NAS 僅在 514 白名單內)。兩次測試均精準攔截並記錄 4 行核心日誌(TCP SYN 逾時重試次數)。本機產生的 journald 上傳走 loopback,不經由 FORWARD 鏈,因此不需特別配置白名單。
此防線在正常運作下應維持零筆紀錄;一旦出現紀錄,即代表有未登錄的主機嘗試送出日誌,或是內網發生橫向探測行為。
分析 QuLog 的連線結構,實際動作字串包含 Connection type: SSH/SFTP、HTTP、HTTPS,動作則區分為 Login Success、Logout、Login Fail。三天內 SSH/SFTP 成功登入高達 4,612 次(主要來自 Day 19 收集端每分鐘執行的 Prometheus 採集腳本);登入失敗僅 2 次,為工程師手動輸入密碼錯誤。
檢視 QNAP 的防護設定(/etc/config/uLinux.conf 中的 [Ban Engine]):

因此,監控告警的門檻定位在捕捉躲過原生防護的慢速滲透:
報表工具 fw-report.py 從 VictoriaLogs 讀取五種來源的統一資料,專注回答三個關鍵安全問題:
實作提醒:在實作基準時間位移時,LogsQL 應寫為
_time:7d offset 24h。開發測試時曾不慎寫反,導致比對區間變成空集合,所有正常設備瞬間全數被判定為「新面孔」。
系統排程於每週一 08:15 自動產出一份 7 天視窗、30 天基線的 Markdown 報表至 /srv/reports/。工程團隊在週會檢視時僅需確認兩件事:
fw-report.py 支援 --textfile 參數,每 10 分鐘執行一次 1 小時時間視窗的統計,並將指標寫入 node_exporter 文字檔目錄。
在 firewall.yml 中配置了六條告警規則:
| 告警規則名稱 | 觸發門檻 | 數值設計脈絡與工程考量 |
|---|---|---|
FwReportStale |
30 分鐘未更新,持續 10 分鐘 | 防範分析管線靜默失效;Alertmanager 配置抑制規則(Inhibit),此規則觸發時自動靜音下游指標 |
FwSourceBurst |
FortiGate: 1,200ufw: 120pve-firewall: 600DOCKER-USER: 30(次/小時,持續 10 分) | 各防護層的日誌限速機制不同:• FortiGate 無本機限速,排除 NTP 後攝影機約 550 筆/時,取約兩倍寬裕值。• ufw 每條規則限速 3 筆/分(約 180 筆/時),設 120 代表遭持續探測達 40 分鐘。• pve-firewall 核心限速 1 筆/秒,設 600 代表探測維持 10 分鐘。• DOCKER-USER 平常應為 0。 |
FwNewSource |
新來源數量 > 0 | 等級為 info,匯總於每日日報,不觸發即時通知 |
NasLoginFailures |
單一 IP 一小時內失敗 10 次 | 識別刻意規避 NAS 5次/分 封鎖機制的慢速密碼探測 |
NasLoginFailuresCritical |
單一 IP 一小時內失敗 50 次 | NAS 內建防禦機制失效,需立即提升至網通設備層阻擋 |
FwSilent |
預期發送阻擋日誌的主機連續 2 小時為 0 筆 | 攝影機每分鐘皆被攔截,若 2 小時完全無日誌,代表設備日誌傳輸中斷或規則遭關閉 |
在最初的告警設計中,FwSilent 永遠不會被觸發。因為當某台主機完全沒有日誌時,LogsQL 的 stats by (host) 根本不會回傳該主機的資料列,textfile 內便不會產出該 Series,Prometheus 自然無法對不存在的序列評估 fw_blocked == 0。
解法:
為 fw-report.py 擴充 --expect 參數(例如配置 FW_EXPECT=fortigate:<設備名稱>)。腳本會主動比對預期清單,若名單內的設備在時窗內沒有任何阻擋,主動輸出數值為 0 的指標(fw_blocked 0),確保 Prometheus 能精準捕捉靜默異常。
在維運中,學會忽略符合預期的阻擋與找出異常同樣重要:
policy 4 攔截是預期防禦,進報表統計但不發出告警,門檻設定在常態流量之上。nas-textfile.sh 每分鐘會 SSH 連線 NAS 一次,每日產生約 1,440 筆成功登入紀錄。分析連線日誌時需先將該 IP 排除。# 1. 編輯白名單設定檔,將 FortiGate IP 加入 SYSLOG_FROM
sudoedit /etc/default/onprem-logs-allowlist
# 2. 重啟白名單服務(LOG_DROPS 預設已啟用為 1)
sudo systemctl restart onprem-logs-allowlist
# 3. 確保本機 journald 送往收集端
sudo ./node/install-journal-upload.sh http://127.0.0.1:9428
# 配置 syslogd2 傳輸端點
config log syslogd2 setting
set status enable
set server "IP"
set mode reliable
set port 514
set format rfc5424
end
# 配置過濾條件,排除高頻 NTP 與 Event 事件
config log syslogd2 filter
set forward-traffic enable
set local-traffic enable
set multicast-traffic disable
set sniffer-traffic disable
set ztna-traffic disable
set http-transaction disable
set anomaly disable
set voip disable
set forti-switch disable
config free-style
edit 1
set category traffic
set filter "(service NTP)"
set filter-type exclude
next
edit 2
set category event
set filter "(type event)"
set filter-type exclude
next
end
end
# 建立報表儲存目錄
sudo install -d -o metrics -g metrics /srv/reports
# 部署 cron 排程(替換為實際 FortiGate 設備名稱)
sed 's/fgt-edge/<FortiGate_Hostname>/' firewall/collector.cron | sudo tee -a /etc/cron.d/onprem-logs
# 手動執行驗證報表產出
VL=http://127.0.0.1:9428 ./firewall/fw-report.py --window 24h --baseline 7d
tests/smoke-firewall.sh(驗證五種來源格式解析、非 Deny 忽略、名單內主機零筆補零輸出、NAS 失敗統計)。promtool test rules 與 promtool check rules。tests/smoke.sh 驗證 ARCHIVE_SKIP 機制,確認排除的來源不會寫入 WORM,且 MANIFEST.tsv 會正確記錄 skipped 狀態。systemd-analyze verify 均無警告。policy DROP: 產生後校正日誌過濾規則。syslogd2 過濾器中來補上對應分類囉。今天我們讓防火牆日誌顯示出「誰在什麼時間去連了哪個連接埠?」,但日誌無法告訴我們「封包內部究竟裝了什麼內容」。
明天 Day 23,我們將前進到底層封包擷取(Packet Capture)。我們將運用最小權限原則在節點上配置 tcpdump,並結合 NAS 內建封包工具,重現並分析 Day 13 發生的 NFS 7分13秒中斷事件與 Day 15 的 corosync 叢集心跳流量,同時將擷取檔案無縫整合進 Day 21 的長期封存管線。